﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.ComponentModel;

namespace PostProcessing
{
    public class Generalizer
    {
        List<Taxonomy> taxonomias;

        BinaryFormatter binaryFormatter;
        FileStream fileStream;

        Log log;

        public Generalizer()
        {
            taxonomias = new List<Taxonomy>();
            binaryFormatter = new BinaryFormatter();
        }

        public bool existeTaxonomias()
        {
            if (taxonomias.Count > 0)
                return true;

            return false;
        }

        /// <summary>
        /// Lê o arquivo de regras e agrupa as regras por consequente gerando vários arquivos na pasta Temp\Conseqs.
        /// </summary>
        /// <param name="consequentes">Lista de possíveis consequentes.</param>
        /// <param name="nomeArqRegras">Nome do arquivo de regras.</param>
        /// <param name="worker">BackgroundWorker utilizado para reportar progresso.</param>
        public static void separarPorConsequentes(string nomeArqRegras, BackgroundWorker worker)
        {
            Rule regraDesmembrada;
            StreamReader readerRegras;
            List<string> blocoRegras;
            Dictionary<string, string> conseqRegras = new Dictionary<string, string>();
            Dictionary<string, StreamWriter> conseqWriter = new Dictionary<string, StreamWriter>();
            int posicaoAtualProcessamento = 0;

            if (!Directory.Exists("Temp"))
                Directory.CreateDirectory("Temp");

            if (!Directory.Exists("Temp\\Conseqs"))
                Directory.CreateDirectory("Temp\\Conseqs");

            foreach (string arquivo in Directory.GetFiles("Temp\\Conseqs"))
            {
                File.Delete(arquivo);
            }

            readerRegras = new StreamReader(nomeArqRegras);

            while (!readerRegras.EndOfStream)
            {
                blocoRegras = Conviction.lerBlocoDados(1000000, readerRegras);

                //Agrupa por consequente as regras lidas.
                foreach (string regraConcatenada in blocoRegras)
                {
                    regraDesmembrada = Rule.desmembraRegra(regraConcatenada);

                    if (!conseqRegras.ContainsKey(regraDesmembrada.consequente))
                    {
                        conseqRegras.Add(regraDesmembrada.consequente, "");
                        conseqWriter.Add(regraDesmembrada.consequente, new StreamWriter("Temp\\Conseqs\\" + regraDesmembrada.consequente + ".txt"));
                    }
                    conseqRegras[regraDesmembrada.consequente] += regraConcatenada + "\r\n";

                    posicaoAtualProcessamento += regraConcatenada.Length;
                    worker.ReportProgress((int)((posicaoAtualProcessamento * 100L) / readerRegras.BaseStream.Length));
                }

                //Escreve as regras processadas em seus respectivos arquivos.
                foreach (KeyValuePair<string, string> grupoConseq in conseqRegras)
                {
                    conseqWriter[grupoConseq.Key].Write(grupoConseq.Value);
                }

                foreach (string grupo in conseqWriter.Keys)
                {
                    conseqRegras[grupo] = "";
                }
            }
            readerRegras.Close();

            //Fecha todos os arquivos gerados.
            foreach (KeyValuePair<string, StreamWriter> arqConseq in conseqWriter)
            {
                arqConseq.Value.Close();
            }
        }

        /// <summary>
        /// Concatena taxonomias à base de dados para posterior cálculo de suporte e confiança de regras generalizadas.
        /// </summary>
        /// <param name="nomeArqDados"></param>
        void concatenarTaxonomiasAosDados(string nomeArqDados, BackgroundWorker worker)
        {
            StreamReader streamReader = new StreamReader(nomeArqDados);
            StreamWriter streamWriter = new StreamWriter(nomeArqDados + ".concat");
            string registroAtual = null;
            string[] registroDesmembrado;
            string registroTax = "";
            bool achouTax = false;

            //Enquanto não for o fim do arquivo de dados...
            while (!streamReader.EndOfStream)
            {
                registroAtual = clearSpaces(streamReader.ReadLine().ToLower());
                registroDesmembrado = registroAtual.Split(new char[] { ' ' });

                //Encontra taxonomias dos itens do registro e constrói nova linha.
                //Para cada item do registro atual...
                foreach (string regItem in registroDesmembrado)
                {
                    //Para cada taxonomia existente...
                    foreach (Taxonomy tax in taxonomias)
                    {
                        if (tax.pertence(regItem, null) != null)
                        {
                            registroTax += tax.cabeca.valor + " ";
                            achouTax = true;
                            break;
                        }
                    }
                    //Se não achou a taxonomia para este item, coloca uma interrogação no registro de taxonomias.
                    if (achouTax == false)
                        registroTax += "? ";
                    else
                        achouTax = false;
                }

                registroTax = registroTax.Remove(registroTax.Length - 1); //Remove espaço que tem no fim da linha.

                streamWriter.WriteLine(registroTax);
                streamWriter.WriteLine(registroAtual);
                streamWriter.WriteLine();

                registroTax = "";

                worker.ReportProgress((int)((streamReader.BaseStream.Position * 100L) / streamReader.BaseStream.Length));
            }

            streamReader.Close();
            streamWriter.Close();
        }

        /// <summary>
        /// Exporta o conjunto de taxonomias para o formato do controle TreeView.
        /// </summary>
        /// <returns></returns>
        public TreeNode gerarTreeView()
        {
            TreeNode treeNode = new TreeNode("Taxonomias");

            foreach (Taxonomy tax in taxonomias)
            {
                treeNode.Nodes.Add(tax.gerarTreeViewNode(null));
            }

            return treeNode;
        }

        /// <summary>
        /// Remove uma determinada taxonomia da memória.
        /// </summary>
        /// <param name="tax"></param>
        public void removerTaxonomia(string tax)
        {
            int indiceTax = indiceTaxonomia(tax);

            if (indiceTax != -1)
            {
                taxonomias.RemoveAt(indiceTax);
                GC.Collect();
            }
            else
                throw new Exception("Taxonomia inexistente. Impossível remover.");
        }

        /// <summary>
        /// Adiciona um item em uma determinada taxonomia.
        /// </summary>
        /// <param name="tax">Taxonomia que receberá o novo item.</param>
        /// <param name="pai">Pai do novo item.</param>
        /// <param name="filho">Novo item.</param>
        public void addNode(string tax, string pai, string filho)
        {
            int index;

            index = indiceTaxonomia(tax);

            if (index != -1)
            {
                if (!existeItemTaxonomia(pai, tax))
                    taxonomias[index].addNode(null, pai);

                taxonomias[index].addNode(pai, filho);
            }
            else
                throw new Exception("Taxonomia inexistente. Impossível adicionar filho.");
        }

        public void removeNode(string tax, string node)
        {
            int taxIndex = indiceTaxonomia(tax);

            try
            {
                if (taxIndex != -1)
                {
                    taxonomias[taxIndex].removeNode(node);
                }
                else
                    throw new Exception("Taxonomia inexistente. Impossível remover item.");
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Descobre a posição de uma determinada taxonomia na lista de taxonomias.
        /// </summary>
        /// <param name="tax">Taxonomia a ser procurada.</param>
        /// <returns>Índice da taxonomia.</returns>
        public int indiceTaxonomia(string tax)
        {
            for (int i = 0; i < taxonomias.Count; i++)
            {
                if (taxonomias[i].cabeca.valor == tax)
                    return i;
            }

            return -1;
        }

        /// <summary>
        /// Encontra a taxonomia de um determinado valor.
        /// </summary>
        /// <param name="valor"></param>
        /// <returns>Taxonomia do valor informado.</returns>
        public string encontraTax(string valor)
        {
            if ((valor != "") && (valor != null))
            {
                foreach (Taxonomy tax in taxonomias)
                {
                    if (tax.pertence(valor, null) != null)
                        return tax.cabeca.valor;
                }
            }

            return null;
        }

        /// <summary>
        /// Encontra o índice da taxonomia de um determinado valor.
        /// </summary>
        /// <param name="valor"></param>
        /// <returns></returns>
        public int encontraTaxIndex(string valor)
        {
            if ((valor != "") && (valor != null))
            {
                for (int i = 0; i < taxonomias.Count; i++)
                {
                    if (taxonomias[i].cabeca.valor == valor)
                        return i;
                    else if (taxonomias[i].pertence(valor, null) != null)
                        return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Verifica se um determinado item existe em uma determinada taxonomia.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="taxonomia"></param>
        /// <returns></returns>
        private bool existeItemTaxonomia(string item, string taxonomia)
        {
            int indexTax = indiceTaxonomia(taxonomia);
            if (taxonomias[indexTax].pertence(item, null) != null)
                return true;

            return false;
        }

        /// <summary>
        /// Adiciona uma taxonomia à lista de taxonomias.
        /// </summary>
        /// <param name="cabeca"></param>
        public void addTaxonomia(string cabeca)
        {
            bool jaExiste = false;

            foreach (Taxonomy taxonomia in taxonomias)
            {
                if (taxonomia.cabeca.valor == cabeca)
                {
                    jaExiste = true;
                    break;
                }
            }

            if (jaExiste)
                throw new Exception("Taxonomia já existe.");
            else
                taxonomias.Add(new Taxonomy(cabeca));
        }

        /// <summary>
        /// Generaliza itens do antecedente da regra.
        /// </summary>
        void generalizarEmLinha(RuleGroups rowGroups, BackgroundWorker worker)
        {
            bool trocarAtualPorTaxonomia = false;
            bool generalizou = false;
            Rule regraAtual;
            List<string> antecedentesRegraAtual;
            string taxonomiaRegraAtual;
            string taxonomiaRegraComparada;
            string regraAntiga = null;
            int posicaoAtualProcessamento = 0;

            foreach (KeyValuePair<string, Dictionary<string, List<Rule>>> taxonomySubGroup in rowGroups.regrasAgrupadas)
            {
                foreach (KeyValuePair<string, List<Rule>> numberSubGroup in taxonomySubGroup.Value)
                {
                    for (int i = 0; i < numberSubGroup.Value.Count; i++)
                    {
                        regraAtual = numberSubGroup.Value[i];
                        antecedentesRegraAtual = regraAtual.AntecedenteDesmembrado;

                        //Se possui mais do que um antecedente entao tenta generalizar
                        if (antecedentesRegraAtual.Count > 1)
                        {
                            regraAtual.gerarAnterior();
                            regraAntiga = regraAtual.ToString();

                            //Para cada antecedente da regra atual...
                            for (int conta = 0; conta < antecedentesRegraAtual.Count; conta++)
                            {
                                taxonomiaRegraAtual = encontraTaxAntec(regraAtual, conta);

                                //Se o antecedente atual pertence a alguma taxonomia...
                                if (taxonomiaRegraAtual != null)
                                {

                                    //Para cada antecedente seguinte...
                                    for (int cont = conta + 1; cont < antecedentesRegraAtual.Count; cont++)
                                    {
                                        taxonomiaRegraComparada = encontraTaxAntec(regraAtual, cont);

                                        //Se possuem as mesmas taxonomias dos antecedentes e o consequente é diferente...
                                        if ((taxonomiaRegraAtual == taxonomiaRegraComparada) && (taxonomiaRegraAtual != encontraTaxConseq(regraAtual)))
                                        {
                                            regraAtual.removeItemAntec(cont);
                                            trocarAtualPorTaxonomia = true;
                                            cont--;
                                        }
                                    }

                                    //Se foram removidos antecedentes de mesma taxonomia, generaliza antecedente atual.
                                    if (trocarAtualPorTaxonomia == true)
                                    {
                                        regraAtual.mudarItemAntec(taxonomiaRegraAtual, conta);
                                        trocarAtualPorTaxonomia = false;

                                        if (generalizou == false)
                                            generalizou = true;

                                        log.regrasGenEmLinha.Add(regraAntiga, regraAtual.ToString());
                                    }
                                }
                            }
                            if (generalizou == false)
                                regraAtual.anteriorAGenEmLinha = null;
                            else
                                generalizou = false;
                        }
                    } 
                }
            }
            posicaoAtualProcessamento++;
            worker.ReportProgress((posicaoAtualProcessamento * 100) / rowGroups.regrasAgrupadas.Count);
        }

        /// <summary>
        /// Le o arquivo de regras colocando essas regras na estrutura RowGroups.
        /// </summary>
        /// <param name="nomeArqRegras"></param>
        /// <returns></returns>
        private RuleGroups gerarRuleGroups(string nomeArqRegras)
        {
            RuleGroups regrasAgrupadas = new RuleGroups(taxonomias);
            StreamReader streamReader = new StreamReader(nomeArqRegras);
            string regraConcatenada;
            Rule regraDesmembrada;

            while (!streamReader.EndOfStream)
            {
                regraConcatenada = clearSpaces(streamReader.ReadLine().ToLower());
                regraDesmembrada = Rule.desmembraRegra(regraConcatenada);

                regrasAgrupadas.addRow(regraDesmembrada);
            }

            streamReader.Close();

            return regrasAgrupadas;
        }

        /// <summary>
        /// Generaliza antecedentes de regras diferentes.
        /// </summary>
        void generalizarPorColuna(RuleGroups rowGroups, int indexAntec, BackgroundWorker worker)
        {
            string taxAntec = null;
            string taxCons = null;
            int posicaoAtualProcessamento = 0;

            foreach (KeyValuePair<string, Dictionary<string, List<Rule>>> taxSubGroup in rowGroups.regrasAgrupadas)
            {
                //Pula o grupo de taxonomia indefinida.
                if (taxSubGroup.Key == "?")
                    continue;

                foreach (KeyValuePair<string, List<Rule>> numSubGroup in taxSubGroup.Value)
                {
                    //Se este subgrupo só tem uma regra, não generaliza.
                    if (numSubGroup.Value.Count > 1)
                    {
                        for (int i = 0; i < numSubGroup.Value.Count; i++)
                        {
                            //Se esta regra possui a posição de antecedente determinada...
                            if (indexAntec < numSubGroup.Value[i].AntecedenteDesmembrado.Count)
                            {
                                taxAntec = encontraTaxAntec(numSubGroup.Value[i], indexAntec);
                                taxCons = encontraTaxConseq(numSubGroup.Value[i]);

                                //Se a taxonomia do antecedente não é a mesma do consequente, generaliza.
                                if ((taxSubGroup.Key != "?") && (taxAntec != taxCons))
                                    numSubGroup.Value[i].gerarGeneralizada(taxSubGroup.Key, indexAntec, log);
                            }
                        }
                    }
                }
            }
            posicaoAtualProcessamento++;
            worker.ReportProgress((posicaoAtualProcessamento * 100) / rowGroups.regrasAgrupadas.Count);
        }

        private int compararStringPorTax(string strA, string strB)
        {
            return encontraTaxIndex(strA) - encontraTaxIndex(strB);
        }

        /// <summary>
        /// Lê arquivo de regras e separa aquelas que não tem possibilidade de serem generalizadas, colocando-as no arquivo de saída.
        /// Um arquivo "[nomeArqRegras].filtrado" é gerado e deve ser utilizado para a generalização.
        /// </summary>
        /// <param name="nomeArqRegras"></param>
        /// <param name="nomeArqSaida"></param>
        private void separarRegrasGeneralizaveis(string nomeArqRegras, string nomeArqSaida)
        {
            List<string> blocoRegras;
            StreamReader readerRegras = new StreamReader(nomeArqRegras);
            StreamWriter writerSaida = new StreamWriter(nomeArqSaida);
            StreamWriter writerArqFiltrado = new StreamWriter(nomeArqRegras + ".filtrado");
            Rule regraDesmembrada;
            string regrasArqFiltrado = "";
            string regrasArqSaida = "";
            bool encontrouTaxonomia = false;

            while (!readerRegras.EndOfStream)
            {
                blocoRegras = Conviction.lerBlocoDados(1000000, readerRegras);

                foreach (string regraConcatenada in blocoRegras)
                {
                    regraDesmembrada = Rule.desmembraRegra(regraConcatenada);

                    foreach (string antecItem in regraDesmembrada.AntecedenteDesmembrado)
                    {
                        if (encontraTax(antecItem) != null)
                        {
                            regraDesmembrada.ordenarAntecedente(compararStringPorTax);
                            regrasArqFiltrado += regraDesmembrada.ToString() + "\r\n";
                            encontrouTaxonomia = true;
                            break;
                        }
                    }

                    if (encontrouTaxonomia == false)
                        regrasArqSaida += regraConcatenada + "\r\n";
                    else
                        encontrouTaxonomia = false;
                }
                blocoRegras.Clear();

                writerArqFiltrado.Write(regrasArqFiltrado);
                writerSaida.Write(regrasArqSaida);

                regrasArqFiltrado = "";
                regrasArqSaida = "";
            }
            writerArqFiltrado.Close();
            writerSaida.Close();
        }

        public Log generalizarConjuntoRegras(string nomeArqDados, string nomeArqRegras, int qtdAntecPossiveis, int confiancaMinima, string nomeArqSaida, BackgroundWorker worker)
        {
            RuleGroups rowGroups;
            List<Rule> regras;
            log = new Log();

            separarRegrasGeneralizaveis(nomeArqRegras, nomeArqSaida);

            separarPorConsequentes(nomeArqRegras + ".filtrado", worker);

            concatenarTaxonomiasAosDados(nomeArqDados, worker);

            foreach (string arquivoConseq in Directory.GetFiles("Temp\\Conseqs"))
            {
                rowGroups = gerarRuleGroups(arquivoConseq);

                generalizarEmLinha(rowGroups, worker);

                //Para cada item de antecedente, agrupa e generaliza em coluna.
                for (int i = 0; i < qtdAntecPossiveis; i++)
                {
                    rowGroups.reagruparPorAntecedente(i);
                    generalizarPorColuna(rowGroups, i, worker);
                }

                regras = rowGroups.gerarListComum();
                rowGroups.regrasAgrupadas.Clear();

                supConfGen(nomeArqDados + ".concat", regras, worker);
                eliminaRegrasBaixaConfianca(regras, confiancaMinima, worker);
                removeGensNaoRepetidas(regras, worker);
                log.contarGenUteis(regras);
                gerarArquivoSaida(regras, nomeArqSaida);
            }

            foreach (string arquivo in Directory.GetFiles("Temp\\Conseqs"))
            {
                File.Delete(arquivo);
            }
            File.Delete(nomeArqDados + ".concat");
            File.Delete(nomeArqRegras + ".filtrado");

            agruparPorTaxAntec(nomeArqSaida);

            return log;
        }

        /// <summary>
        /// Verifica se um determinado item de uma determinada regra foi generalizado ou não.
        /// </summary>
        /// <param name="regra"></param>
        /// <param name="itemAntec"></param>
        /// <returns></returns>
        bool itemAntecGeneralizado(string itemAntec)
        {
            foreach (Taxonomy tax in taxonomias)
            {
                if (tax.cabeca.valor == itemAntec)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Verifica se um determinado item de antecedente pertence a um dos registros (normal e de taxonomia).
        /// </summary>
        /// <param name="itemAntec"></param>
        /// <param name="regisTaxs"></param>
        /// <param name="regisNormal"></param>
        /// <returns></returns>
        bool existeItemNoRegistro(string itemAntec, string[] regisTaxs, string[] regisNormal)
        {
            if (itemAntecGeneralizado(itemAntec))
            {
                //Para cada item do registro de taxonomias...
                foreach (string itemRegis in regisTaxs)
                {
                    if (itemAntec == itemRegis)
                        return true;
                }
            }
            else
            {
                //Para cada item do registro normal...
                foreach (string itemRegis in regisNormal)
                {
                    if (itemAntec == itemRegis)
                        return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Para cada item de dado existente na base de dados (.CONCAT), armazena em quais linhas do arquivo ele aparece a fim de fornecer um meio 
        /// de se fazer calculo de suporte e confiança.
        /// </summary>
        /// <param name="nomeArqDados"></param>
        /// <returns></returns>
        private Dictionary<string, List<int>> contarItensDados(string nomeArqDadosConcat, out int qtdRegs)
        {
            Dictionary<string, List<int>> contadores = new Dictionary<string, List<int>>();
            List<string> blocoDados;
            StreamReader readerDados = new StreamReader(nomeArqDadosConcat);
            int linhaAtual = 0;
            string[] registroTax;
            string[] registroNormal;


            while (!readerDados.EndOfStream)
            {
                blocoDados = Conviction.lerBlocoDados(1000000, readerDados);

                for (int i = 0; i < blocoDados.Count; i += 2)
                {
                    linhaAtual++;
                    registroTax = blocoDados[i].Split(new char[] { ' ' });
                    if ((i + 1) < blocoDados.Count)
                        registroNormal = blocoDados[i + 1].Split(new char[] { ' ' });
                    else
                        registroNormal = readerDados.ReadLine().Split(new char[] { ' ' });

                    if (registroNormal != null && registroNormal.Length > 0)
                    {
                        foreach (string regItem in registroTax)
                        {
                            if (regItem != "?")
                            {
                                if (contadores.ContainsKey(regItem))
                                {
                                    if(!contadores[regItem].Contains(linhaAtual))
                                        contadores[regItem].Add(linhaAtual);
                                }
                                else
                                {
                                    contadores.Add(regItem, new List<int>());
                                    contadores[regItem].Add(linhaAtual);
                                }
                            }
                        }

                        foreach (string regItem in registroNormal)
                        {
                            if (contadores.ContainsKey(regItem))
                            {
                                if (!contadores[regItem].Contains(linhaAtual))
                                    contadores[regItem].Add(linhaAtual);
                            }
                            else
                            {
                                contadores.Add(regItem, new List<int>());
                                contadores[regItem].Add(linhaAtual);
                            }
                        }
                    }
                    else
                        throw new Exception("Erro no método contarItensDados. Parece que o arquivo concat não estava num formato correto.");

                    registroNormal = null;
                    registroTax = null;
                }
                blocoDados.Clear();
            }

            qtdRegs = linhaAtual;

            return contadores;
        }

        /// <summary>
        /// Dado algumas listas de ocorrencias, conta as coincidencias entre elas.
        /// Utilizado para calcular suporte com a estrutura "Dictionary[string, List[int]]".
        /// </summary>
        /// <param name="ocorrencias"></param>
        /// <returns></returns>
        private int contaCoincidencias(List<List<int>> ocorrencias)
        {
            int coincidencias = 0;
            bool todosPossuem = true;

            //Se existe mais do que uma lista de ocorrências...
            if (ocorrencias.Count > 1)
            {
                //Para cada ocorrencia da primeira lista, testa se as outras listas também possuem esta ocorrencia (linha).
                foreach (int linha in ocorrencias[0])
                {
                    //Da segunda lista em diante...
                    for (int i = 1; i < ocorrencias.Count; i++)
                    {
                        //Se essa lista não contém a ocorrencia atual, sinaliza e não conta uma coincidencia.
                        if (!ocorrencias[i].Contains(linha))
                        {
                            todosPossuem = false;
                            break;
                        }
                    }

                    if (todosPossuem)
                        coincidencias++;
                    else
                        todosPossuem = true;
                }
            }
            else
                return ocorrencias[0].Count;

            return coincidencias;
        }

        private void calculaSupConf(Rule regra, Dictionary<string, List<int>> ocorrencias, int qtdRegs)
        {
            List<List<int>> auxCoincidencias = new List<List<int>>();
            int qtdOcorrenciasAntec = 0;
            int qtdOcorrenciasRegra = 0;

            foreach (string antecItem in regra.AntecedenteDesmembrado)
            {
                auxCoincidencias.Add(ocorrencias[antecItem]);
            }

            qtdOcorrenciasAntec = contaCoincidencias(auxCoincidencias);
            regra.suporte = (float)Math.Round(((double)qtdOcorrenciasAntec / qtdRegs) * 100.0);

            auxCoincidencias.Add(ocorrencias[regra.consequente]);
            qtdOcorrenciasRegra = contaCoincidencias(auxCoincidencias);
            regra.confianca = (float)Math.Round(((double)qtdOcorrenciasRegra / qtdOcorrenciasAntec) * 100.0);

            auxCoincidencias.Clear();
        }

        private void supConfGen(string nomeArqDadosConcat, List<Rule> listaRegras, BackgroundWorker worker)
        {
            int posicaoAtualProcessamento = 0;
            int qtdRegs;

            Dictionary<string, List<int>> ocorrencias = contarItensDados(nomeArqDadosConcat, out qtdRegs);

            foreach (Rule regra in listaRegras)
            {
                if (regra.generalizada != null)
                {
                    calculaSupConf(regra.generalizada, ocorrencias, qtdRegs);
                }
                else if (regra.anteriorAGenEmLinha != null)
                {
                    calculaSupConf(regra, ocorrencias, qtdRegs);
                }

                posicaoAtualProcessamento++;
                worker.ReportProgress((posicaoAtualProcessamento * 100) / listaRegras.Count);
            }
        }

        /// <summary>
        /// Calcula o suporte de uma regra que foi generalizada.
        /// </summary>
        void supConfGeneralizado(List<Rule> listRegras, string nomeArqDados, BackgroundWorker worker)
        {
            string[] registroAtualTaxs;
            string[] registroAtualNormal;
            StreamReader streamReader = new StreamReader(nomeArqDados + ".concat");
            float qtdTotalRegistros = 0;
            int contIgualdade = 0;
            float qtdRegisSuportam = 0;
            float qtdAcertos = 0;
            bool calcularTotalRegs = true;
            int posicaoAtualProcessamento = 0;

            //Para cada regra...
            foreach (Rule regra in listRegras)
            {
                if (regra.generalizada != null)
                {
                    //Le todo o arquivo de dados contabilizando os valores necessários para o cálculo de suporte.
                    while (!streamReader.EndOfStream)
                    {
                        registroAtualTaxs = clearSpaces(streamReader.ReadLine().ToLower()).Split(new char[] { ' ' });
                        registroAtualNormal = clearSpaces(streamReader.ReadLine().ToLower()).Split(new char[] { ' ' });
                        streamReader.ReadLine();    //Lê linha vazia.

                        //Para cada item do antecedente da regra...
                        foreach (string itemAntec in regra.generalizada.AntecedenteDesmembrado)
                        {
                            if (existeItemNoRegistro(itemAntec, registroAtualTaxs, registroAtualNormal))
                                contIgualdade++;
                        }

                        //Se todos os itens do antecedente da regra existem neste registro atual...
                        if (contIgualdade == regra.generalizada.AntecedenteDesmembrado.Count)
                        {
                            qtdRegisSuportam++;

                            //Verifica se consequente da regra existe no registro normal atual.
                            if (existeItemNoRegistro(regra.generalizada.consequente, null, registroAtualNormal))
                                qtdAcertos++;
                        }

                        if (calcularTotalRegs)
                            qtdTotalRegistros++;

                        contIgualdade = 0;
                    }
                    //Volta ao início do arquivo.
                    streamReader.BaseStream.Seek(0, SeekOrigin.Begin);

                    if (qtdRegisSuportam != 0)
                    {
                        regra.generalizada.suporte = (float)Math.Round((double)((qtdRegisSuportam / qtdTotalRegistros) * 100));
                        regra.generalizada.confianca = (float)Math.Round((double)((qtdAcertos / qtdRegisSuportam) * 100));
                    }
                    else
                    {
                        regra.generalizada.suporte = 0;
                        regra.generalizada.confianca = 0;
                    }

                    if(calcularTotalRegs)
                        calcularTotalRegs = false;
                }

                qtdRegisSuportam = 0;
                qtdAcertos = 0;

                posicaoAtualProcessamento++;
                worker.ReportProgress((posicaoAtualProcessamento * 100) / listRegras.Count);
            }

            streamReader.Close();
        }

        /// <summary>
        /// Elimina as regras generalizadas que não possuem uma confiança satisfatória.
        /// </summary>
        /// <param name="listRegras"></param>
        /// <param name="confiancaMinima"></param>
        void eliminaRegrasBaixaConfianca(List<Rule> listRegras, float confiancaMinima, BackgroundWorker worker)
        {
            for (int i = 0; i < listRegras.Count; i++)
            {
                if (listRegras[i].generalizada != null)
                {
                    if (listRegras[i].generalizada.confianca < confiancaMinima)
                    {
                        log.regrasGenConfBaixa.Add(listRegras[i].ToString(), listRegras[i].generalizada.ToString());

                        listRegras[i].generalizada = null;
                    }
                }
                worker.ReportProgress((i * 100) / listRegras.Count);
            }

            GC.Collect();
        }

        /// <summary>
        /// Escreve uma determinada lista de regras em um determinado arquivo texto.
        /// </summary>
        /// <param name="listRegras"></param>
        /// <param name="nomeArqSaida"></param>
        void gerarArquivoSaida(List<Rule> listRegras, string nomeArqSaida)
        {
            StreamWriter streamWriter = new StreamWriter(nomeArqSaida, true);
            List<int> indicesRegrasEscreverRemover = new List<int>();
            int qtdRegrasRemovidas = 0;

            //Enquanto existirem regras a serem escritas no arquivo de saída...
            while (listRegras.Count > 0)
            {
                //Se a primeira regra da fila não possui uma versão generalizada, escreve-a e remove-a da fila.
                if (listRegras[0].generalizada == null)
                {
                    streamWriter.WriteLine(listRegras[0].ToString());
                    if(listRegras[0].anteriorAGenEmLinha != null)
                        streamWriter.WriteLine("  " + listRegras[0].anteriorAGenEmLinha.ToString());
                    listRegras.RemoveAt(0);
                }
                else
                {
                    //Se a fila de regras possui mais do que uma regra...
                    if (listRegras.Count > 1)
                    {
                        //Para cada regra seguinte...
                        for (int i = 1; i < listRegras.Count; i++)
                        {
                            //Se a regra atual possui versão generalizada...
                            if (listRegras[i].generalizada != null)
                            {
                                //Se as generalizações são iguais, marca regra para ser escrita e removida.
                                if (Rule.comparaRegras(listRegras[0].generalizada, listRegras[i].generalizada))
                                    indicesRegrasEscreverRemover.Add(i);
                            }
                        }

                        //Escreve a regra generalizada e as respectivas regras específicas encontradas.
                        streamWriter.WriteLine(listRegras[0].generalizada.ToString());
                        streamWriter.WriteLine("  " + listRegras[0].ToString());

                        if (listRegras[0].anteriorAGenEmLinha != null)
                            streamWriter.WriteLine("    " + listRegras[0].anteriorAGenEmLinha.ToString());

                        listRegras.RemoveAt(0);
                        qtdRegrasRemovidas++;
                        foreach (int indice in indicesRegrasEscreverRemover)
                        {
                            streamWriter.WriteLine("  " + listRegras[indice - qtdRegrasRemovidas].ToString());

                            if (listRegras[indice - qtdRegrasRemovidas].anteriorAGenEmLinha != null)
                                streamWriter.WriteLine("    " + listRegras[indice - qtdRegrasRemovidas].anteriorAGenEmLinha.ToString());

                            listRegras.RemoveAt(indice - qtdRegrasRemovidas);
                            qtdRegrasRemovidas++;
                        }
                        indicesRegrasEscreverRemover.Clear();
                        qtdRegrasRemovidas = 0;
                    }
                    else
                    {
                        //ESTA SITUAÇÃO NÃO PODE ACONTECER. PORÉM É ÚTIL PARA DEPURAÇÃO DE CÓDIGO.

                        streamWriter.WriteLine(listRegras[0].generalizada.ToString());
                        streamWriter.WriteLine("  " + listRegras[0].ToString());

                        if(listRegras[0].anteriorAGenEmLinha != null)
                            streamWriter.WriteLine("    " + listRegras[0].anteriorAGenEmLinha.ToString());
                    }
                }
            }

            streamWriter.Close();
        }

        /// <summary>
        /// Encontra taxonomia de um determinado antecedente de uma determinada regra.
        /// </summary>
        /// <param name="regra"></param>
        /// <param name="indexAntecedente"></param>
        /// <returns></returns>
        string encontraTaxAntec(Rule regra, int indexAntecedente)
        {
            foreach (Taxonomy tax in taxonomias)
            {
                //Se a regra atual possui o antecedente especificado...
                if (indexAntecedente < regra.AntecedenteDesmembrado.Count)
                {
                    if (tax.pertence(regra.AntecedenteDesmembrado[indexAntecedente], null) != null)
                        return tax.cabeca.valor;
                }
            }

            return null;
        }

        /// <summary>
        /// Encontra taxonomia do consequente da regra.
        /// </summary>
        /// <param name="regra"></param>
        /// <returns></returns>
        string encontraTaxConseq(Rule regra)
        {
            foreach (Taxonomy tax in taxonomias)
            {
                if (tax.pertence(regra.consequente, null) != null)
                    return tax.cabeca.valor;
            }

            return null;
        }

        /// <summary>
        /// Salva a lista de taxonomias em um arquivo binário.
        /// </summary>
        /// <param name="fileSystemPath">Caminho e nome do arquivo a ser gerado.</param>
        public void saveTaxsToBinaryFile(string fileSystemPath)
        {
            using (fileStream = new FileStream(fileSystemPath, FileMode.Create))
            {
                binaryFormatter.Serialize(fileStream, taxonomias);
            }
        }

        /// <summary>
        /// Recupera a lista de taxonomias a partir de um arquivo binário.
        /// </summary>
        /// <param name="fileSystemPath">Caminho/nome do arquivo a ser interpretado.</param>
        /// <returns></returns>
        public void openBinaryFile(string fileSystemPath)
        {
            using (fileStream = new FileStream(fileSystemPath, FileMode.Open))
            {
                taxonomias = (List<Taxonomy>) binaryFormatter.Deserialize(fileStream);
            }
        }

        /// <summary>
        /// Dado um grupo de regras, remove as repetidas.
        /// </summary>
        public void removeGensNaoRepetidas(List<Rule> regras, BackgroundWorker worker)
        {
            bool regraGenRepetida = false; 

            //Para cada regra...
            for (int i = 0; i < regras.Count; i++)
            {
                //Se esta regra possui uma versão generalizada...
                if (regras[i].generalizada != null)
                {
                    //Para todas as regras seguintes...
                    for (int j = 0; j < regras.Count; j++)
                    {
                        //Se esta outra regra possui uma versão generalizada...
                        if ((regras[j].generalizada != null) && (Rule.comparaRegras(regras[i], regras[j]) == false))
                        {
                            //Se as duas regras são iguais, sinaliza a repetição.
                            if(Rule.comparaRegras(regras[i].generalizada,regras[j].generalizada))
                                regraGenRepetida = true;
                        }
                    }
                    //Se a regra generalizada atual não se repetiu no conjunto, anula a generalização.
                    if (regraGenRepetida == false)
                    {
                        log.regrasGenInuteis.Add(regras[i].ToString(), regras[i].generalizada.ToString());

                        regras[i].generalizada = null;
                    }
                    else
                        regraGenRepetida = false;
                }
                worker.ReportProgress((i * 100) / regras.Count);
            }
            GC.Collect();
        }

        public void agruparPorTaxAntec(string nomeArqRegrasGen)
        {
            separarPorSequenciaTax(nomeArqRegrasGen);
            juntarArquivos(nomeArqRegrasGen.Replace(".txt", ".agrupado.txt"), Directory.GetFiles("Temp\\Seqs"));
        }

        /// <summary>
        /// Separa as regras de acordo com as taxonomias dos itens de antecedente.
        /// São gerados arquivos na pasta Temp\Seqs.
        /// </summary>
        /// <param name="nomeArqRegrasGen"></param>
        private void separarPorSequenciaTax(string nomeArqRegrasGen)
        {
            StreamReader readerRegras = new StreamReader(nomeArqRegrasGen);
            StreamWriter writer = new StreamWriter(nomeArqRegrasGen.Replace(".txt", ".agrupado.txt"));
            List<string> blocoRegras;
            Rule regraDesmembrada;
            Dictionary<string, string> seqRegras = new Dictionary<string, string>();
            Dictionary<string, StreamWriter> seqWriter = new Dictionary<string, StreamWriter>();
            string sequenciaAtual;
            string regrasSemTax = "";

            if (!Directory.Exists("Temp"))
                Directory.CreateDirectory("Temp");

            if (!Directory.Exists("Temp\\Seqs"))
                Directory.CreateDirectory("Temp\\Seqs");

            foreach (string arquivo in Directory.GetFiles("Temp\\Seqs"))
            {
                File.Delete(arquivo);
            }

            while (!readerRegras.EndOfStream)
            {
                blocoRegras = Conviction.lerBlocoDados(1000000, readerRegras);

                foreach (string regraConcatenada in blocoRegras)
                {
                    regraDesmembrada = Rule.desmembraRegra(regraConcatenada);
                    sequenciaAtual = sequenciaTaxonomias(regraDesmembrada);

                    if (sequenciaAtual != "")
                    {
                        if (!seqRegras.ContainsKey(sequenciaAtual))
                        {
                            seqRegras.Add(sequenciaAtual, "");
                            seqWriter.Add(sequenciaAtual, new StreamWriter("Temp\\Seqs\\" + sequenciaAtual + ".txt"));
                        }

                        seqRegras[sequenciaAtual] += regraConcatenada + "\r\n";
                    }
                    else
                        regrasSemTax += regraConcatenada + "\r\n";
                }

                writer.Write(regrasSemTax);
                foreach (KeyValuePair<string, string> seqReg in seqRegras)
                {
                    seqWriter[seqReg.Key].Write(seqReg.Value);
                }

                regrasSemTax = "";
                foreach (string seq in seqWriter.Keys)
                {
                    seqRegras[seq] = "";
                }
            }
            readerRegras.Close();
            seqRegras.Clear();

            writer.Close();
            foreach (string seq in seqWriter.Keys)
            {
                seqWriter[seq].Close();
            }
            seqWriter.Clear();
        }

        /// <summary>
        /// Junta vários arquivos em um só.
        /// </summary>
        /// <param name="nomeArqSaida"></param>
        /// <param name="arquivos"></param>
        private void juntarArquivos(string nomeArqSaida, string[] arquivos)
        {
            StreamReader reader;
            StreamWriter writer = new StreamWriter(nomeArqSaida, true);
            string regras = "";
            List<string> blocoDados;

            foreach (string arq in arquivos)
            {
                reader = new StreamReader(arq);

                while (!reader.EndOfStream)
                {
                    blocoDados = Conviction.lerBlocoDados(1000000, reader);
                    regras = string.Join("\r\n", blocoDados.ToArray());
                    writer.Write(regras);
                }
                reader.Close();

                File.Delete(arq);
                writer.Write("\r\n");
                writer.Write("\r\n");
            }
            writer.Close();
        }

        /// <summary>
        /// Descobre o indice da taxonomia de cada item de antecedente da regra e gera uma string com esses índices.
        /// </summary>
        /// <param name="regra"></param>
        /// <returns></returns>
        private string sequenciaTaxonomias(Rule regra)
        {
            string sequencia = "";
            int taxIndex;

            foreach (string itemAntec in regra.AntecedenteDesmembrado)
            {
                taxIndex = encontraTaxIndex(itemAntec);

                if(taxIndex >= 0)
                    sequencia += taxIndex.ToString();
            }

            return sequencia;
        }

        private string clearSpaces(string linha)
        {
            string linhaLimpa = linha.Replace("  ", " ");

            if (linha != linhaLimpa)
                return clearSpaces(linhaLimpa);

            return linhaLimpa;
        }
    }
}
